/*
装饰器本身是一个函数
 */

// 类装饰器
/*
类装饰器接收的参数是构造函数
 */
function testDecorator (constructor:new (...args:any[])=>any) {
  constructor.prototype.getName = () => {
    console.log('dell')
  }
  console.log('decorator')
}

function testDecorator1 (constructor:any) {
  console.log('decorator1')
}

@testDecorator1
@testDecorator
class Test {}
(new Test() as any).getName()

// 闭包作装饰器
function decor1 (flag:boolean) {
  if (flag) {
    return function (constructor:any) {
      constructor.prototype.info = () => {
        console.log('dell')
      }
    }
  }
  return function (constructor:any) {}
}
@decor1(true)
class Test1 {}

// 类装饰器泛型
function testDecor<T extends new (...args:any[])=>any>(constructor:T) {
  return class extends constructor {
    name = 'lee';
    getName () {
      console.log(this.name)
    }
  }
}

@testDecor
class Person {
  name: string;
  constructor (name:string) {
    this.name = name
  }
}
(new Person('sam') as any).getName() // 这种写法TS识别不到getName(), 改进方式是以下写法

// 装饰器工厂函数写法
function decoratorFactory () {
  return function <T extends new (...args:any[])=>any>(constructor:T) {
    return class extends constructor {
      name = 'lee';
      getName () {
        console.log(this.name)
      }
    }
  }
}

// 利用工厂函数对匿名类进行装饰
const Person1 = decoratorFactory()(class {
  name: string;
  constructor (name:string) {
    this.name = name
  }
})

new Person1('sam').getName() // 拓展的方法可以显示出来

// 方法装饰器
function funcDecor (target: any, key:string, descriptor:PropertyDescriptor) {
  /* target参数是方法对应的那个类的prototype
    如果方法是静态方法，target是那个类的构造函数
   */
  console.log(target)
  console.log(key) // key是被装饰的方法名
  console.log(descriptor) // js的属性描述
  // descriptor.writable = false
  // 直接修改方法
  const oldVal = descriptor.value as (...args:any[])=> any
  descriptor.value = function (...args:any[]) {
    return oldVal.apply(this, args) + 'NO!!!!!!!!!!!'
  }
}

class UObj {
  name:string
  constructor (name:string) {
    this.name = name
  }

  // 方法装饰器
  @funcDecor
  getName () {
    return this.name
  }
}

const objU = new UObj('sam')
console.log(objU.getName())

// 访问器的装饰器
/*
set和get不能同时绑定装饰器.
 */
console.log('===============vistor====================')
function visitDecor (target:any, key:string, descriptor:PropertyDescriptor) {
  // descriptor.writable = false
}

class XObj {
  private _name:string;
  constructor (name:string) {
    this._name = name
  }

  get name () {
    return this._name
  }

  @visitDecor
  set name (name:string) {
    this._name = name
  }
}

const teeObj = new XObj('dell')
teeObj.name = 'sam'
console.log(teeObj.name)

// https://blog.csdn.net/HTK217/article/details/104687214

// 属性装饰器
function propDecorator (target:any, key:string):any {
  // 创建新的属性描述符来覆盖(返回)
  /* const descriptor:PropertyDescriptor = {
    writable: false
  }
  return descriptor */
  // 因为target是类对应原型，因此修改是原型上的属性，而不是实例上的属性
  target[key] = 'NoNoNO'
}

class XaObj {
  @propDecorator
  name='Dell'
}
const xao = new XaObj()
xao.name = 'Samuel'
console.log(xao.name) // Samuel
console.log((xao as any).__proto__.name) // NoNoNO

// 类的方法的参数装饰器
/**
 *
 * @param target 原型
 * @param method 方法名
 * @param paramIndex 参数的位置索引
 */
function paramDecorator (target: any, method: string, paramIndex:number) {
  console.log(target, method, paramIndex)
}

class Sth {
  getInfo (name:string, @paramDecorator age: number) {
    console.log(name, age)
  }
}

new Sth().getInfo('Sam', 30)

// 装饰器实例
console.log('=========================================================')
/*
如果多个方法可能抛出异常，而不想在每个方法内部都写try--catch
使用方法装饰器解决
 */
const userInfo:any = undefined

function methodDecorThrowErr (target:any, key:string, desciptor: PropertyDescriptor) {
  const fn = desciptor.value
  desciptor.value = function () {
    try {
      fn.call(this)
    } catch (e) {
      console.log('error in Method')
    }
  }
}

class TestMethodDecor {
  @methodDecorThrowErr
  getName () {
    return userInfo.name
  }

  @methodDecorThrowErr
  getAge () {
    return userInfo.age
  }
}

const obkj = new TestMethodDecor()
obkj.getName()
obkj.getAge()
